Tagを定数としてスクリプトに出力する拡張機能作成

July 23, 2022


あるGameObjectのTagをスクリプトで判定する時、文字列直打ちでは柔軟性に欠けます 手動で定数化した場合も更新の保守しなければ行けない為、ここはUnityEditor拡張機能に頼り自動生成してもらうようにします

Tagとは

GameObject名の下にあるTag一覧からタグの変更ができます

50526CC8083F7F24B127C7DDFCB4832F

スクリプトでは tag もしくは ComparateTag から判定を行うことができます

private bool IsItem(GameObject obj)
{
		return obj.CompareTag("Item");
}

タグ一覧から任意のタグを追加できます

4B672B7351D3AB1B291BF568DB532911

スクリプトからは

var tags = InternalEditorUtility.tags;

で取得することが可能です。 これを使用し追加されたタグをスクリプトに出力します

Script Template

雛形からスクリプトを生成するようにします

  • namespace #NAMESPACE#
  • クラス名 #SCRIPTNAME#
  • 定義 #PARAM#
namespace #NAMESPACE#
{
	public partial class #SCRIPTNAME#
	{
#PARAM#
	}
}

で囲まれた文字列をEditor拡張機能から置換してスクリプトを作成します

上記を ConstClass.txt のような名前で適当なフォルダに突っ込んでおきます

ScriptCreator

実際の拡張機能クラスを作成します まずは、

  • テンプレートを読み込む
  • #で囲まれた文字列を置換する
  • 出力する

を行う ScriptCreator の作成を行います

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;

public class ScriptCreator
{
    public const string TemplateDirectory = "Assets/Editor/ScriptTemplates/";

    public class Param
    {
        public string ScriptName;
        public string TemplateName;
        public string DirectoryPath;
        public Dictionary<string, string> ReplaceDict = new Dictionary<string, string>();
    }

    public static void Execute(Param param)
    {
        // テンプレート
        var templatePath = $"{TemplateDirectory}/{param.TemplateName}.txt";

        string scriptText;
        using (var streamReader = new StreamReader(templatePath))
        {
            scriptText = streamReader.ReadToEnd();
        }

        // 各項目を置換
        foreach (var pair in param.ReplaceDict)
        {
            scriptText = scriptText.Replace(pair.Key, pair.Value);
        }

        // 出力
        var exportPath = $"{param.DirectoryPath}/{param.ScriptName}.cs";

        File.WriteAllText(exportPath, scriptText, System.Text.Encoding.UTF8);
        AssetDatabase.Refresh(ImportAssetOptions.ImportRecursive);
    }
}

行っていることは単純で、 ReplaceDict Dictionaryで渡された文字を置換します。

次にTagを読み込み、必要なパラメータをScriptCreatorにわたす TagDefinieCreator クラスを作成します

using System.Collections.Generic;
using UnityEditor;
using System;
using System.Text;
using System.Linq;
using UnityEditorInternal;

public class TagDefineCreator
{
    public const string SaveDirectory = "Assets/Scripts/";
    public const string ScriptName = "TagDefine";
    public const string TemplateName = "DefineClass";

    [MenuItem("Tools/ScriptCreator/TagDefineCreate")]
    public static void Execute()
    {
        var valueDict = InternalEditorUtility.tags.ToDictionary(x => x, x => x);

        // 定数作成メソッドにあとは任せる
        Create(valueDict, ScriptName, "Test");
    }


    public static void Create<T>(Dictionary<string, T> valueDict, string scriptName, string namespceName)
    {
        var sb = new StringBuilder();

        // 最大の文字数
        var keyLengthMax = valueDict.Keys.Max(x => x.Length);

        var typeCode = Type.GetTypeCode(typeof(T));

        bool isFirst = true;

        foreach (var pair in valueDict)
        {
            var value = pair.Value.ToString();

            (string TypeStr, string ValueStr) str = typeCode switch
                {
                    TypeCode.Int32 => ("int", value),
                    TypeCode.Int64 => ("long", value),
                    TypeCode.Double => ("double", value),
                    TypeCode.String => ("string", $"\"{value}\""), // ""で囲む
                    _ => (null, null)
                };

            if (!isFirst)
            {
                sb.AppendLine();
            }

            isFirst = false;

            sb.Append($"\t\tpublic const {str.TypeStr} {pair.Key} = {str.ValueStr};");
        }

        var createParam = new ScriptCreator.Param();
        createParam.ReplaceDict["#PARAM#"] = sb.ToString();
        createParam.ReplaceDict["#SCRIPTNAME#"] = scriptName;
        createParam.ReplaceDict["#NAMESPACE#"] = namespceName;

        createParam.DirectoryPath = SaveDirectory;
        createParam.ScriptName = scriptName;
        createParam.TemplateName = TemplateName;

        ScriptCreator.Execute(createParam);
    }
}

Tools/ScriptCreator/TagDefineCreate で、メニューから実行された場合、TagをDictionaryに変換し、必要なパラメータと共にScriptCreatorに渡しています

今回はstring型ですが、 int などを定数化したい場合を考え

var typeCode = Type.GetTypeCode(typeof(T));

で型を取得することにしました。

後はScriptCreator.ParamのDictionaryに置き換える文字列を格納し実行しています

結果、以下のScriptが作成されました

namespace Test
{
	public partial class TagDefine
	{
		public const string Untagged = "Untagged";
		public const string Respawn = "Respawn";
		public const string Finish = "Finish";
		public const string EditorOnly = "EditorOnly";
		public const string MainCamera = "MainCamera";
		public const string Player = "Player";
		public const string GameController = "GameController";
	}
}

終わり

今回の機能はかなり簡素な作りにしています。 他にも

  • Layers
  • Sorint Layers

も定数化したいケースが出てくると思います

その時は TagDefineCreator の中身から定数化に必要な部分を抜き出し DefineCreator というクラスを一枚噛ませるようにすれば処理の共有化が出来ます